﻿#include "aoi.hh"
#include "../object_pool.hpp"
#include <algorithm>

namespace kratos { namespace corelib {

void AoiEntity::move(std::int32_t x, std::int32_t y) {
    scene_->move(this, x, y);
}

void AoiEntity::setAoi(std::int32_t aoi) {
    aoi_ = aoi;
}

AoiScene::AoiScene(std::int32_t startx, std::int32_t starty,
    std::int32_t endx, std::int32_t endy, std::int32_t splitRange)
    : headX_(nullptr), headY_(nullptr), startx_(startx), starty_(starty), endx_(endx),
      endy_(endy), splitRange_(splitRange) {
    auto count = static_cast<size_t>((endx_ - startx_) / splitRange_);
    splitX_.reserve(count);
    splitY_.reserve(count);
    AoiEntity* last = nullptr;
    for (std::int32_t i = startx_; i < endx_; i += splitRange_) {
        auto entity = allocate<AoiEntity>(this, 0, i, i, splitRange_, AoiEntity::TYPE_ANCHOR);
        splitX_.push_back(entity);
        splitY_.push_back(entity);
        if (last) {
            last->nextx_ = entity;
            last->nexty_ = entity;
        }
        entity->prevx_ = last;
        entity->prevy_ = last;
        last = entity;
    }
}

AoiScene::~AoiScene() {
    AoiEntity* head = headX_;
    AoiEntity* next = nullptr;
    while (head) {
        next = head->nextx_;
        dispose(head);
        head = next;
    }
    head = headY_;
    next = nullptr;
    while (head) {
        next = head->nexty_;
        dispose(head);
        head = next;
    }
    entityMap_.clear();
}

AoiEntity* AoiScene::enter(uint64_t id, std::int32_t x, std::int32_t y, std::int32_t aoi) {
    auto locx = (size_t)((x - startx_) / splitRange_);
    auto locy = (size_t)((y - starty_) / splitRange_);
    if (locx >= splitX_.size()) {
        locx = splitX_.size() - 1;
    }
    if (locy >= splitY_.size()) {
        locy = splitY_.size() - 1;
    }
    auto entity = allocate<AoiEntity>(this, id, x, y, aoi, AoiEntity::TYPE_NORMAL);
    addNodeX(splitX_[locx], entity);
    addNodeY(splitY_[locy], entity);
    entityMap_[id] = entity;
    return entity;
}

void AoiScene::leave(AoiEntity* entity) {
    auto it = entityMap_.find(entity->getID());
    if (it == entityMap_.end()) {
        return;
    }
    leave(it);
}

void AoiScene::leave(uint64_t id) {
    auto it = entityMap_.find(id);
    if (it == entityMap_.end()) {
        return;
    }
    leave(it);
}

void AoiScene::leave(EntityMap::iterator it) {
    if (it == entityMap_.end()) {
        return;
    }
    auto entity = it->second;
    delNodeX(entity);
    delNodeY(entity);
    entityMap_.erase(it);
    dispose(entity);
}

void AoiScene::move(AoiEntity* entity, std::int32_t x, std::int32_t y) {
    delNodeX(entity);
    delNodeY(entity);
    entity->setX(x);
    entity->setY(y);
    auto locx = (size_t)((x - startx_) / splitRange_);
    auto locy = (size_t)((y - starty_) / splitRange_);
    if (locx >= splitX_.size()) {
        locx = splitX_.size() - 1;
    }
    if (locy >= splitY_.size()) {
        locy = splitY_.size() - 1;
    }
    addNodeX(splitX_[locx], entity);
    addNodeY(splitY_[locy], entity);
}

void AoiScene::move(uint64_t id, std::int32_t x, std::int32_t y) {
    auto it = entityMap_.find(id);
    if (it == entityMap_.end()) {
        return;
    }
    move(it->second, x, y);
}

void AoiScene::overlapCircle(std::int32_t x, std::int32_t y, std::int32_t radius,
    EntityVector & overlappedResult, std::uint64_t except) {
    auto locx = (size_t)((x - startx_) / splitRange_);
    auto locy = (size_t)((y - starty_) / splitRange_);
    if (locx >= splitX_.size()) {
        locx = splitX_.size() - 1;
    }
    if (locy >= splitY_.size()) {
        locy = splitY_.size() - 1;
    }
    auto entity = allocate<AoiEntity>(this, 0, x, y, radius, AoiEntity::TYPE_NORMAL);
    addNodeX(splitX_[locx], entity);
    addNodeY(splitY_[locy], entity);
    AoiEntity* prevx = entity->prevx_;
    AoiEntity* nextx = entity->nextx_;
    AoiEntity* prevy = entity->prevy_;
    AoiEntity* nexty = entity->nexty_;
    while (prevx) {
        if (!prevx->isSkip()) {
            if (entity->inCircle(prevx)) {
                insertResult(prevx, overlappedResult, except);
            } else {
                break;
            }
        } else {
            if (!entity->inCircle(prevx)) {
                break;
            }
        }
        prevx = prevx->prevx_;
    }
    while (nextx) {
        if (!nextx->isSkip()) {
            if (entity->inCircle(nextx)) {
                insertResult(nextx, overlappedResult, except);
            } else {
                break;
            }
        } else {
            if (!entity->inCircle(nextx)) {
                break;
            }
        }
        nextx = nextx->nextx_;
    }
    while (prevy) {
        if (!prevy->isSkip()) {
            if (entity->inCircle(prevy)) {
                insertResult(prevy, overlappedResult, except);
            } else {
                break;
            }
        } else {
            if (!entity->inCircle(prevy)) {
                break;
            }
        }
        prevy = prevy->prevy_;
    }
    while (nexty) {
        if (!nexty->isSkip()) {
            if (entity->inCircle(nexty)) {
                insertResult(nexty, overlappedResult, except);
            } else {
                break;
            }
        } else {
            if (!entity->inCircle(nexty)) {
                break;
            }
        }
        nexty = nexty->nexty_;
    }
    delNodeX(entity);
    delNodeY(entity);
    dispose(entity);
}

void AoiScene::overlapSquare(std::int32_t x, std::int32_t y, std::int32_t length,
    EntityVector & overlappedResult, std::uint64_t except) {
    auto locx = (size_t)((x - startx_) / splitRange_);
    auto locy = (size_t)((y - starty_) / splitRange_);
    if (locx >= splitX_.size()) {
        locx = splitX_.size() - 1;
    }
    if (locy >= splitY_.size()) {
        locy = splitY_.size() - 1;
    }
    auto entity = allocate<AoiEntity>(this, 0, x, y, length, AoiEntity::TYPE_NORMAL);
    addNodeX(splitX_[locx], entity);
    addNodeY(splitY_[locy], entity);
    AoiEntity* prevx = entity->prevx_;
    AoiEntity* nextx = entity->nextx_;
    AoiEntity* prevy = entity->prevy_;
    AoiEntity* nexty = entity->nexty_;
    while (prevx) {
        if (!prevx->isSkip()) {
            if (entity->inAoi(prevx)) {
                insertResult(prevx, overlappedResult, except);
            } else {
                break;
            }
        } else {
            if (!entity->inAoi(prevx)) {
                break;
            }
        }
        prevx = prevx->prevx_;
    }
    while (nextx) {
        if (!nextx->isSkip()) {
            if (entity->inAoi(nextx)) {
                insertResult(nextx, overlappedResult, except);
            } else {
                break;
            }
        } else {
            if (!entity->inAoi(nextx)) {
                break;
            }
        }
        nextx = nextx->nextx_;
    }
    while (prevy) {
        if (!prevy->isSkip()) {
            if (entity->inAoi(prevy)) {
                insertResult(prevy, overlappedResult, except);
            } else {
                break;
            }
        } else {
            if (!entity->inAoi(prevy)) {
                break;
            }
        }
        prevy = prevy->prevy_;
    }
    while (nexty) {
        if (!nexty->isSkip()) {
            if (entity->inAoi(nexty)) {
                insertResult(nexty, overlappedResult, except);
            } else {
                break;
            }
        } else {
            if (!entity->inAoi(nexty)) {
                break;
            }
        }
        nexty = nexty->nexty_;
    }
    delNodeX(entity);
    delNodeY(entity);
    dispose(entity);
}

AoiEntity * AoiScene::getEntity(std::uint64_t id) {
    auto it = entityMap_.find(id);
    if (it == entityMap_.end()) {
        return nullptr;
    }
    return it->second;
}

std::size_t AoiScene::getSize() {
    return entityMap_.size();
}

void AoiScene::insertResult(AoiEntity * entity, EntityVector & overlappedResult, std::uint64_t except) {
    if (entity->getID() == except) {
        return;
    }
    for (auto in : overlappedResult) {
        if (entity->getID() == in->getID()) {
            return;
        }
    }
    overlappedResult.push_back(entity);
}

AoiEntity * AoiScene::getNearestAnchorX(std::int32_t x, std::int32_t y) {
    auto locx = (size_t)((x - startx_) / splitRange_);
    if (locx >= splitX_.size()) {
        locx = splitX_.size() - 1;
    }
    return splitX_[locx];
}

AoiEntity * AoiScene::getNearestAnchorY(std::int32_t x, std::int32_t y) {
    auto locy = (size_t)((y - starty_) / splitRange_);
    if (locy >= splitY_.size()) {
        locy = splitY_.size() - 1;
    }
    return splitY_[locy];
}

void AoiScene::addNodeX(AoiEntity* head, AoiEntity* entity) {
    while (head) {
        if (head->x_ > entity->x_) {
            entity->prevx_ = head->prevx_;
            if (head->prevx_) {
                head->prevx_->nextx_ = entity;
            }
            head->prevx_ = entity;
            entity->nextx_ = head;
            return;
        } else {
            if (head->nextx_) {
                head = head->nextx_;
            } else {
                head->nextx_ = entity;
                entity->prevx_ = head;
                break;
            }
        }
    }
}

void AoiScene::addNodeY(AoiEntity* head, AoiEntity* entity) {
    while (head) {
        if (head->y_ > entity->y_) {
            entity->prevy_ = head->prevy_;
            if (head->prevy_) {
                head->prevy_->nexty_ = entity;
            }
            head->prevy_ = entity;
            entity->nexty_ = head;
            return;
        }  else {
            if (head->nexty_) {
                head = head->nexty_;
            } else {
                head->nexty_ = entity;
                entity->prevy_ = head;
                break;
            }
        }
    }
}

void AoiScene::delNodeX(AoiEntity* entity) {
    if (entity->prevx_) {
        entity->prevx_->nextx_ = entity->nextx_;
    }
    if (entity->nextx_) {
        entity->nextx_->prevx_ = entity->prevx_;
    }
    entity->nextx_ = nullptr;
    entity->prevx_ = nullptr;
}

void AoiScene::delNodeY(AoiEntity* entity) {
    if (entity->prevy_) {
        entity->prevy_->nexty_ = entity->nexty_;
    }
    if (entity->nexty_) {
        entity->nexty_->prevy_ = entity->prevy_;
    }
    entity->nexty_ = nullptr;
    entity->prevy_ = nullptr;
}

}
}
